﻿using NAudio.CoreAudioApi;
using NAudio.Dsp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Shapes;
using Rectangle = System.Windows.Shapes.Rectangle;

namespace AudioVisualization
{
    /// <summary>
    /// 可视化助手
    /// </summary>
    public class VisualizationHelper
    {
        private int bitsPerSample;
        private int sampleRate;
        private int channelCount;

        private float curveMultiple = 1;
        private double frequencyPerIndex;
        private double curveFrequencyEnd = 2500d;
        // 音频数据
        private float[] Samples;
        private double[] DftData;
        //lock
        private object SamplesLock = new object();
        private object DftDataLock = new object();


        private List<System.Windows.Shapes.Rectangle> _rectangleList;
        private WaveFormArgs _waveFormArgs;
        private WasapiCapture _capture;

        public VisualizationHelper(StackPanel parentPanel, WaveFormArgs args)
        {
            InitializeAudioWaveForm(parentPanel, args);
            InitializeCapture();
        }

        private void InitializeCapture()
        {
            _capture?.Dispose();
            _capture = new WasapiCapture();

            _capture.DataAvailable += Capture_DataAvailable;

            bitsPerSample = _capture.WaveFormat.BitsPerSample;
            sampleRate = _capture.WaveFormat.SampleRate;
            channelCount = _capture.WaveFormat.Channels;
            Timer timer = new Timer();
            timer.Interval = 15;
            timer.Tick += RealTimeHandler_Tick;
            timer.Tick += RenderPanel;
            timer.Start();
        }

        private void InitializeAudioWaveForm(StackPanel stackPanel, WaveFormArgs args)
        {
            _waveFormArgs = args;
            stackPanel.Children.Clear();
            _rectangleList = new List<Rectangle>();
            for (int i = 0; i < _waveFormArgs.RecNum; i++)
            {
                var rectangle = new Rectangle()
                {
                    Name = "wave" + i,
                    Margin = new Thickness(0, 0, _waveFormArgs.RecMargin, 0),
                    Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(0, 0, 0)),
                    Width = args.RecWidth,
                    Height = args.RecHeight
                };
                _rectangleList.Add(rectangle);
                stackPanel.Children.Add(rectangle);
            }
            _rectangleList.Last().Margin = new Thickness(0, 0, 0, 0);
        }

        /// <summary>
        /// LUOGUANG: 录制的音频格式，路径等等
        /// </summary>
        public void BeginRecord()
        {
            _capture.StartRecording();
        }

        public void StopRecord()
        {
            _capture.StopRecording();
        }

        private void Capture_DataAvailable(object sender, NAudio.Wave.WaveInEventArgs e)
        {
            if (e.BytesRecorded == 0)
                return;
            int bytesPerSample = bitsPerSample / 8;
            lock (SamplesLock)
                Samples = Enumerable
                              .Range(0, e.BytesRecorded / 4)
                              .Select(i => BitConverter.ToSingle(e.Buffer, i * 4)).ToArray();   // 获取采样
        }

        private void RealTimeHandler_Tick(object sender, EventArgs e)
        {
            if (Samples == null)
                return;
            float[][] chanelSamples;
            lock (SamplesLock)
                chanelSamples = Enumerable
                    .Range(0, channelCount)
                    .Select(i => Enumerable
                        .Range(0, Samples.Length / channelCount)
                        .Select(j => Samples[i + j * channelCount])
                        .ToArray())
                    .ToArray();

            float[] chanelAverageSamples = Enumerable
                .Range(0, chanelSamples[0].Length)
                .Select(i => Enumerable
                    .Range(0, channelCount)
                    .Select(j => chanelSamples[j][i])
                    .Average())
                .ToArray();

            float[] sampleSrc = chanelAverageSamples;
            int log = (int)Math.Floor(Math.Log(sampleSrc.Length, 2));
            float[] filledSamples = new float[(int)Math.Pow(2, log)];
            Array.Copy(sampleSrc, filledSamples, Math.Min(sampleSrc.Length, filledSamples.Length));   // 填充数据
            Complex[] complexSrc = filledSamples.Select((v, i) => new Complex() { X = v }).ToArray();
            FastFourierTransform.FFT(false, log, complexSrc);     // 进行傅里叶变换
            double[] result = complexSrc.Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y)).ToArray();    // 取得结果
            double[] reresult = result.Take(result.Length / 2).ToArray();                            // 取一半

            frequencyPerIndex = (double)sampleRate / filledSamples.Length;
            UpdateDftData(reresult, 0.8, 0.5);
        }

        private void RenderPanel(object s, EventArgs args)
        {
            if (DftData == null || DftData.Length == 0 || frequencyPerIndex == 0)
                return;
            lock (DftDataLock)
            {
                int hz2500index = (int)(curveFrequencyEnd / frequencyPerIndex);
                double[] resultPaint = DftData.Take(hz2500index).ToArray();

                // LUOGUANG: 长宽可以自定义？
                //Graphics g = bufferedGraphics.Graphics;
                float dataRight = resultPaint.Length;
                float panelRight = _waveFormArgs.RecWidth;// DrawPanel.Width;
                float panelHeight = _waveFormArgs.RecHeight;// DrawPanel.Height;


                PointF[] points = Enumerable.Range(0, resultPaint.Length).Select(i =>
                    new PointF(
                        (int)(i / dataRight * panelRight),
                        panelHeight - (float)resultPaint[i] * curveMultiple))
                    .ToArray();

                
                UpdateUI(points);
            }
        }

        private void UpdateUI(PointF[] points)
        {
            var pointArray = points.Take(_waveFormArgs.RecNum).ToArray();

            for (int i = 0; i < pointArray.Length; i++)
            {
                // LUOGUANG: 由于不知道的出来的Y的具体含义，暂通过扩大展示频图
                float y = (_waveFormArgs.RecHeight - pointArray[i].Y) * 4f;
                _rectangleList[i].Height = y >= _waveFormArgs.RecHeight ? _waveFormArgs.RecHeight : y;
            }
        }

        /// <summary>
        /// 平滑的更新 DftData
        /// </summary>
        /// <param name="newData"></param>
        /// <param name="upParam"></param>
        /// <param name="downParam"></param>
        /// <returns></returns>
        private double[] UpdateDftData(double[] newData, double upParam = 1, double downParam = 1)
        {
            if (DftData == null || DftData.Length == 0)
                return DftData = newData.Select(v => v * upParam).ToArray();
            lock (DftDataLock)
            {
                try
                {
                    return DftData = newData.Select((v, i) =>
                    {
                        double lastData = GetCurvePoint(DftData, (double)i / newData.Length);
                        double incre = v - lastData;
                        return lastData + incre * (incre > 0 ? upParam : downParam);
                    }).ToArray();
                }
                catch (IndexOutOfRangeException)
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// 从 double 中平滑的获取一个值
        /// 索引以百分比的形式指定, 基本原理时调用 GetCurvePoint
        /// </summary>
        /// <param name="sequence"></param>
        /// <param name="percent"></param>
        /// <returns></returns>
        double GetCurvePoint(double[] sequence, double percent)
        {
            return IndexCurvePoint(sequence, percent * sequence.Length);
        }

        /// <summary>
        /// 从 double 中平滑的取得一个值
        /// 例如 curve[0] = 0, curve[1] = 100, 那么通过此方法访问 curve[0.5], 可得到 50
        /// </summary>
        /// <param name="curve"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        double IndexCurvePoint(double[] curve, double index)
        {
            int
                floor = (int)Math.Min(Math.Floor(index), curve.Length - 1),
                ceiling = (int)Math.Min(Math.Ceiling(index), curve.Length - 1);
            if (floor == ceiling)
                return curve[floor];
            double
                left = curve[floor],
                right = curve[ceiling];
            return left + (right - left) * (index - floor);
        }

        public class WaveFormArgs
        {
            public int RecNum { get; set; }
            public int RecWidth { get; set; }
            public int RecHeight { get; set; }
            public int RecMargin { get; set; }
        }
    }
}
